#include "function_group.h"
#include "logger.h"

#include "nosal/log/logger.h"
#include "nosal/log/logging.h"

#include <algorithm>

namespace netaos {
namespace nosal {
namespace exec {
namespace internal {

namespace
{
netaos::nosal::log::Logger& GetLogger()
{
    static netaos::nosal::log::Logger& logger = netaos::nosal::log::CreateLogger("FG", "FunctionGroup", netaos::nosal::exec::internal::kLogLevel);
    return logger;
}
}  // namespace

const config::State FunctionGroup::kOffState("Off");
const config::State FunctionGroup::kUndefinedState("Undefined");

FunctionGroup::FunctionGroup(const std::string& name, const std::vector<config::State>& states)
    : name_(name)
    , all_states_(states)
    , current_state_(&kOffState)
{ }

const std::string& FunctionGroup::GetName() const
{
    return name_;
}

const std::string& FunctionGroup::GetCurrentStateName() const
{
    return current_state_->GetName();
}

const std::vector<config::State>& FunctionGroup::GetAllStates() const
{
    return all_states_;
}

const config::State& FunctionGroup::GetCurrentState() const
{
    return *current_state_;
}

void FunctionGroup::SetCurrentState(const std::string& state_name)
{
    const config::State* new_state = FindState(state_name);

    if (nullptr == new_state) {
        GetLogger().LogError() << "Function group" << name_ << "does not have state" << state_name;
        return;
    }

    current_state_ = new_state;

    // reset warning flag.
    was_warning_issued_before_ = false;

    if (execution_error_) {
        // A configured state is never an undefined state
        execution_error_.reset();
    }
}

void FunctionGroup::UpdateState()
{
    if (IsCurrentStateOffOrUndefined()) {
        // the state can be changed only by calling SetCurrentState()
        return;
    }

    // check if there are any failed processes
    netaos::nosal::core::Optional<const std::shared_ptr<StartupConfiguration>> failed_process = FindFailedProcess();

    if (failed_process) {
        GetLogger().LogError() << "Function Group" << name_ + "." + GetCurrentStateName()
                               << "is failed because of process" << (*failed_process)->GetName();
        execution_error_ = (*failed_process)->GetExecutionError();
        current_state_ = &kUndefinedState;
    }
}

bool FunctionGroup::IsTransitionFinished() const
{
    if (IsCurrentStateOffOrUndefined()) {
        return true;
    }

    const std::string& current_state = GetCurrentStateName();

    const auto it = startup_configurations_.find(current_state);
    if (it == startup_configurations_.cend()) {
        GetLogger().LogWarn() << "Empty Function Group State" << name_ + "." + current_state;
        return true;
    }

    bool is_transition_finished = true;

    for (const auto& item : it->second) {
        const std::shared_ptr<StartupConfiguration> startup_configuration = item;

        const ProcessState process_state = startup_configuration->GetState();

        if (!(process_state == ProcessState::kRunning || process_state == ProcessState::kTerminated
                || process_state == ProcessState::kTerminating)) {

            is_transition_finished = false;
            break;
        }
    }

    return is_transition_finished;
}

bool FunctionGroup::IsCurrentStateFailed() const
{
    return (kUndefinedState.GetName() == current_state_->GetName());
}

std::vector<std::shared_ptr<StartupConfiguration>> FunctionGroup::GetStartupConfigurations(
    const std::string& state_name) const
{
    std::vector<std::shared_ptr<StartupConfiguration>> result;

    if (!HasState(state_name)) {
        GetLogger().LogError() << "Function Group" << name_ << "does not have state" << state_name;
        return result;
    }

    const auto it = startup_configurations_.find(state_name);
    if (it != startup_configurations_.cend()) {

        result.reserve(it->second.size());
        result.insert(result.end(), it->second.cbegin(), it->second.cend());
    }

    return result;
}

void FunctionGroup::AddStartupConfig(const std::string& state_name,
    std::shared_ptr<StartupConfiguration> startup_configuration)
{
    if (!HasState(state_name)) {
        GetLogger().LogError() << "Function Group" << name_ << "does not have state" << state_name;
        return;
    }

    auto& stateStartupConfigurations = startup_configurations_[state_name];

    if (stateStartupConfigurations.count(startup_configuration) != 0) {
        GetLogger().LogWarn() << "Process" << startup_configuration->GetName()
                              << "is already added to FunctionGroupState" << name_ + "." + state_name;
        return;
    }

    stateStartupConfigurations.insert(startup_configuration);
}

bool FunctionGroup::IsStartupConfigurationRequiredNow(
    std::shared_ptr<StartupConfiguration> const startup_configuration) const
{
    if (IsCurrentStateOffOrUndefined()) {
        return false;
    }

    const std::string& current_state = GetCurrentStateName();

    const auto startup_configurations_it = startup_configurations_.find(current_state);
    if (startup_configurations_it == startup_configurations_.cend()) {
        GetLogger().LogWarn() << "Empty Function Group State" << name_ + "." + current_state;
        return false;
    }

    const std::set<std::shared_ptr<StartupConfiguration>>& required_configurations = startup_configurations_it->second;

    return required_configurations.count(startup_configuration) != 0;
}

netaos::nosal::core::Optional<netaos::nosal::exec::ExecutionError> FunctionGroup::GetExecutionError() const
{
    return execution_error_;
}

void FunctionGroup::SetUndefinedFunctionGroupState(const netaos::nosal::exec::ExecutionError& error)
{
    unhandled_state_fail_ = true;
    current_state_ = &kUndefinedState;
    execution_error_ = error;
}

void FunctionGroup::RemoveStartupConfigFromGroup(const std::string& startup_configuration_name)
{
    for (auto& state_map_instance : startup_configurations_) {
        std::set<std::shared_ptr<StartupConfiguration>>& set_of_configs_per_state = state_map_instance.second;
        auto it = std::find_if(set_of_configs_per_state.begin(),
            set_of_configs_per_state.end(),
            [startup_configuration_name](std::shared_ptr<StartupConfiguration> config) {
                return startup_configuration_name == config->GetName();
            });
        if (it != set_of_configs_per_state.end()) {
            GetLogger().LogInfo() << "Process" << startup_configuration_name << "deleted from FunctionGroup" << name_
                                  << "and its state" << state_map_instance.first;
            set_of_configs_per_state.erase(it);
        }
    }
}

bool FunctionGroup::IsStartupConfigurationPartOfGroup(const std::string& startup_configuration_name)
{
    for (const auto& stateMapElement : startup_configurations_) {
        auto& startup_configs_in_state = stateMapElement.second;
        auto it = std::find_if(startup_configs_in_state.begin(),
            startup_configs_in_state.end(),
            [&startup_configuration_name](std::shared_ptr<StartupConfiguration> config) {
                return config->GetName() == startup_configuration_name;
            });
        if (it != startup_configs_in_state.end()) {
            return true;
        }
    }
    return false;
}

bool FunctionGroup::HasUnhandledStateFailedEvent()
{
    if (unhandled_state_fail_) {
        unhandled_state_fail_ = false;
        return true;
    }

    return false;
}

const config::State* FunctionGroup::FindState(const std::string& state_name) const
{
    auto is_same_name = [state_name](const config::State& state) { return state_name == state.GetName(); };

    auto state_it = std::find_if(all_states_.cbegin(), all_states_.cend(), is_same_name);

    return state_it != all_states_.cend() ? &(*state_it) : nullptr;
}

bool FunctionGroup::HasState(const std::string& state_name) const
{
    return state_name == kOffState.GetName() || FindState(state_name) != nullptr;
}

netaos::nosal::core::Optional<const std::shared_ptr<StartupConfiguration>> FunctionGroup::FindFailedProcess()
{
    const std::string& current_state = GetCurrentStateName();

    const auto it = startup_configurations_.find(current_state);
    if ((it == startup_configurations_.cend()) && (!was_warning_issued_before_)) {
        GetLogger().LogWarn() << "Empty Function Group State" << name_ + "." + current_state;
        was_warning_issued_before_ = true;
        return {};
    }

    for (const auto& item : it->second) {
        const std::shared_ptr<StartupConfiguration> startup_configuration = item;
        const ProcessState process_state = startup_configuration->GetState();
        const TerminationCause termination_cause = startup_configuration->GetTerminationCause();

        if ((process_state == ProcessState::kTerminated) && (termination_cause == TerminationCause::kAbnormalExit)) {
            return startup_configuration;
        }
    }

    return {};
}

bool FunctionGroup::IsCurrentStateOffOrUndefined() const
{
    const std::string& current_state_name = current_state_->GetName();
    return ((kOffState.GetName() == current_state_name) || (kUndefinedState.GetName() == current_state_name));
}

}  // namespace internal
}  // namespace exec
}  // namespace nosal
}  // namespace netaos
